#ifndef	__clipper_H__
#define __clipper_H__

#include "rendVect.hpp"
#include "rendBox.hpp"
#include <float.h>

namespace rend
{

template < unsigned NUM_PLANES_T >
class Clipper
{
public:

	enum { NUM_PLANES = NUM_PLANES_T };

	// delimiting planes storage;
	// a delimiting plane is represented by the vector of its parametric equation,
	// Ax + By + Cz + D = 0, i.e. [ normal.x, normal.y, normal.z, -distance ]

	const vect4&	getPlane(const unsigned planeIdx) const;
	void			setPlane(const unsigned planeIdx, const vect4& plane);

	// referential point internal to the current clipping volume; not automatically updated!
	const vect3&	getInternalPoint() const;
	void			setInternalPoint(const vect3& internal);

	// delimiting planes face inward, i.e. their 'positive' and 'negative' halfspaces
	// are those of non-negative and negative distances to the plane, respectively;
	// note that the plane surface itself is considered 'positive'

	float			getDistanceToPlane(const unsigned planeIdx,
									   const vect3& point) const;
	// juxtaposition tests;
	// the 'inside' of the frustum is the intersection of the positive half-spaces
	// of all delimiting planes; 'intersection' comes as a superset to 'inside', so
	// when something is 'inside' it also 'intersects', but the inverse is not true

	bool			isPointInside(const vect3& point) const;

	bool			isTriangleInside(const vect3& point0,
									 const vect3& point1,
									 const vect3& point2) const;

	bool			isTriangleIntersecting(const vect3& point0,
										   const vect3& point1,
										   const vect3& point2) const;

	bool			isQuadInside(const vect3& point0,
								 const vect3& point1,
								 const vect3& point2,
								 const vect3& point3) const;

	bool			isQuadIntersecting(const vect3& point0,
									   const vect3& point1,
									   const vect3& point2,
									   const vect3& point3) const;

	bool			isAABoxInside(const vect3& point0,
								  const vect3& point1) const;

	bool			isAABoxIntersecting(const vect3& point0,
										const vect3& point1) const;

	bool			isSphereInside(const vect3& point,
								   const float radius) const;

	bool			isSphereIntersecting(const vect3& point,
										 const float radius) const;

	Clipper();

protected:

	float			mPlane[NUM_PLANES_T][4];
	vect3			mInternalPoint;
};


template < unsigned NUM_PLANES_T >
Clipper< NUM_PLANES_T >::Clipper()
: mInternalPoint(FLT_MAX, FLT_MAX, FLT_MAX)
{
	static const vect4 zero(0, 0, 0, 0);

	for (unsigned i = 0; i < NUM_PLANES_T; ++i)
		setPlane(i, zero);
}


template < unsigned NUM_PLANES_T >
const vect4& Clipper< NUM_PLANES_T >::getPlane(const unsigned planeIdx) const
{
	assert(planeIdx < NUM_PLANES_T);

	return vect4::cast(mPlane[planeIdx]);
}


template < unsigned NUM_PLANES_T >
void Clipper< NUM_PLANES_T >::setPlane(const unsigned planeIdx,
									   const vect4& plane)
{
	assert(planeIdx < NUM_PLANES_T);

	const float rn = 1.f / vect3::castU(vect4::decast(plane)).norm();

	vect4::cast(mPlane[planeIdx]).mul(plane, rn);
}


template < unsigned NUM_PLANES_T >
float Clipper< NUM_PLANES_T >::getDistanceToPlane(const unsigned planeIdx,
												  const vect3& point) const
{
	assert(planeIdx < NUM_PLANES_T);

	return vect3::castU(mPlane[planeIdx]).dp(point) + mPlane[planeIdx][3];
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isPointInside(const vect3& point) const
{
	return isSphereInside(point, 0.f);
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isTriangleInside(const vect3& point0,
											   const vect3& point1,
											   const vect3& point2) const
{
	return isPointInside(point0) &&
		   isPointInside(point1) &&
		   isPointInside(point2);
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isTriangleIntersecting(const vect3& point0,
													 const vect3& point1,
													 const vect3& point2) const
{
	const unsigned __maxIntersections = NUM_PLANES_T * 2;
	const unsigned __maxCumulativeVerts = (6 + NUM_PLANES_T) * (NUM_PLANES_T + 1) >> 1;

	float buffer[__maxIntersections][3];
	unsigned nbuff = 0;

	const float (* poly[__maxCumulativeVerts])[3] =
	{
		&vect3::decast(point0),
		&vect3::decast(point1),
		&vect3::decast(point2)
	};
	unsigned start = 0;
	unsigned size = 3;

	for (unsigned i = 0; i < NUM_PLANES_T; ++i)
	{
		clip_by_plane(mPlane[i], poly, start, size, buffer, nbuff);

		if (!size)
			return false;
	}

	return true;
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isQuadInside(const vect3& point0,
										   const vect3& point1,
										   const vect3& point2,
										   const vect3& point3) const
{
	return isPointInside(point0) &&
		   isPointInside(point1) &&
		   isPointInside(point2) &&
		   isPointInside(point3);
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isQuadIntersecting(const vect3& point0,
												 const vect3& point1,
												 const vect3& point2,
												 const vect3& point3) const
{
	const unsigned __maxIntersections = NUM_PLANES_T * 2;
	const unsigned __maxCumulativeVerts = (8 + NUM_PLANES_T) * (NUM_PLANES_T + 1) >> 1;

	float buffer[__maxIntersections][3];
	unsigned nbuff = 0;

	const float (* poly[__maxCumulativeVerts])[3] =
	{
		&vect3::decast(point0),
		&vect3::decast(point1),
		&vect3::decast(point2),
		&vect3::decast(point3)
	};
	unsigned start = 0;
	unsigned size = 4;

	for (unsigned i = 0; i < NUM_PLANES_T; ++i)
	{
		clip_by_plane(mPlane[i], poly, start, size, buffer, nbuff);

		if (!size)
			return false;
	}

	return true;
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isAABoxInside(const vect3& point0,
											const vect3& point1) const
{
	vect3 p01(point1.get(0), point0.get(1), point0.get(2));
	vect3 p02(point0.get(0), point1.get(1), point0.get(2));
	vect3 p03(point0.get(0), point0.get(1), point1.get(2));

	vect3 p11(point0.get(0), point1.get(1), point1.get(2));
	vect3 p12(point1.get(0), point0.get(1), point1.get(2));
	vect3 p13(point1.get(0), point1.get(1), point0.get(2));

	return isPointInside(point0) &&
		   isPointInside(p01) &&
		   isPointInside(p02) &&
		   isPointInside(p03) &&
		   isPointInside(point1) &&
		   isPointInside(p11) &&
		   isPointInside(p12) &&
		   isPointInside(p13);
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isAABoxIntersecting(const vect3& point0,
												  const vect3& point1) const
{
	vect3 p01(point1.get(0), point0.get(1), point0.get(2));
	vect3 p02(point0.get(0), point1.get(1), point0.get(2));
	vect3 p03(point0.get(0), point0.get(1), point1.get(2));

	vect3 p11(point0.get(0), point1.get(1), point1.get(2));
	vect3 p12(point1.get(0), point0.get(1), point1.get(2));
	vect3 p13(point1.get(0), point1.get(1), point0.get(2));

	return isQuadIntersecting(point1, p13, p01, p12) ||		// +x
		   isQuadIntersecting(point0, p03, p11, p02) ||		// -x
		   isQuadIntersecting(point1, p11, p02, p13) ||		// +y
		   isQuadIntersecting(point0, p01, p12, p03) ||		// -y
		   isQuadIntersecting(point1, p12, p03, p11) ||		// +z
		   isQuadIntersecting(point0, p02, p13, p01) ||		// -z
		   Box3(point0, point1).isInside(mInternalPoint);	// when frustum is entirely inside the box being tested
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isSphereInside(const vect3& point,
											 const float radius) const
{
	for (unsigned i = 0; i < NUM_PLANES_T; ++i)
		if (getDistanceToPlane(i, point) <= radius)
			return false;

	return true;
}


template < unsigned NUM_PLANES_T >
bool Clipper< NUM_PLANES_T >::isSphereIntersecting(const vect3& point,
												   const float radius) const
{
	return isSphereInside(point, -radius);
}


template < unsigned NUM_PLANES_T >
const vect3& Clipper< NUM_PLANES_T >::getInternalPoint() const
{
	return mInternalPoint;
}


template < unsigned NUM_PLANES_T >
void Clipper< NUM_PLANES_T >::setInternalPoint(const vect3& internal)
{
	assert(isPointInside(internal));

	mInternalPoint = internal;
}

} // namespace rend

#endif // __clipper_H__
